
import codecs

##class StyTextElem(object):
##    __slots__ = ()
##    def __init__(self, text, 

class StyledText(object):
    __slots__ = ('_len', '_elems')
    def __init__(self):
        self._len = 0
        self._elems = []
    def __len__(self):
        return self._len
    def __iter__(self):
        raise NotImplementedError("Can't iterate over StyledText characters")
    def __str__(self):
        return ''.join([el[0] for el in self._elems])
    def __getitem__(self, idx):
        if idx < 0:
            idx += self._len
        if idx < 0 or idx >= self._len:
            raise IndexError('%s index out of range' % self.__class__.__name__)
        for elem in self._elems:
            cnt = len(elem[0])
            if idx < cnt:
                return (elem[0][idx],) + elem[1:]
            idx -= cnt
    def __getslice__(self, idx, jdx):
        res = self.__class__()
        for elem in self._elems:
            if jdx <= 0:
                break
            cnt = len(elem[0])
            if idx <= 0 and jdx >= cnt:
                res.append(elem)
            elif 0 <= idx < cnt:
                res.append((elem[0][idx:jdx],) + elem[1:])
            elif jdx < cnt:
                res.append((elem[0][:jdx],) + elem[1:])
            idx -= cnt
            jdx -= cnt
        return res
    def __iadd__(self, other):
        pass
    def append(self, elem):
        if elem[0]:
            self._len += len(elem[0])
            last = self._elems[-1] if self._elems else None
            if last and last[1:] == elem[1:]:
                self._elems[-1] = (last[0] + elem[0],) + last[1:]
            else:
                self._elems.append(elem)
    def iterelems(self):
        return iter(self._elems)

class BufferElement(object):
    __slots__ = ('text',)

class RingBuffer(object):
    def __init__(self, size):
        self._buffer = []
        self._size = size
        self._head = -1
    def __len__(self):
        return len(self._buffer)
    def __getitem__(self, idx):
        if idx < 0:
            idx += len(self._buffer)
        if idx < 0 or idx >= len(self._buffer):
            raise IndexError('%s index out of range' % self.__class__.__name__)
        idx = (self._head - idx) % self._size
        return self._buffer[idx]
    def __getslice__(self, idx, jdx):
        idx = (self._head - idx) % self._size
        jdx = (self._head - jdx) % self._size
        print idx, jdx
        if idx >= jdx:
            return self._buffer[idx:jdx:-1]
        else:
            return self._buffer[idx::-1] + self._buffer[:jdx:-1]
    def push(self, data):
        self._head = (self._head + 1) % self._size
        if self._head >= len(self._buffer):
            self._buffer.append(data)
        else:
            self._buffer[self._head] = data

# ===================================================================

# Control function definitions, according to ISO/IEC-6429 and ECMA-48

def valmap(cls):
    byval = {}
    for key, val in vars(cls).iteritems():
        if type(val) is str:
            if val in byval:
                raise RuntimeError('value %r defined multiple times in %r' % (val, cls.__name__))
            byval[val] = key
            setattr(cls, key, unicode(val))
        elif type(val) is set:
            setattr(cls, key, {unicode(x) for x in val})
    def getname(val):
        return byval.get(val) or ('ESC ' if cls is CtrlFn else '') + \
                                  ' '.join('x{:02X}'.format(ord(x)) for x in val)
    setattr(cls, 'getname', staticmethod(getname))
    return cls

@valmap
class CtrlFn:
# -------------------- C0 control functions --------------------
    NUL = '\x00'
    SOH = '\x01'
    STX = '\x02'
    ETX = '\x03'
    EOT = '\x04'
    ENQ = '\x05'
    ACK = '\x06'
    BEL = '\x07'
    BS  = '\x08'
    HT  = '\x09'
    LF  = '\x0A'
    VT  = '\x0B'
    FF  = '\x0C'
    CR  = '\x0D'
    SO  = '\x0E'
    SI  = '\x0F'
    DLE = '\x10'
    DC1 = '\x11'
    DC2 = '\x12'
    DC3 = '\x13'
    DC4 = '\x14'
    NAK = '\x15'
    SYN = '\x16'
    ETB = '\x17'
    CAN = '\x18'
    EM  = '\x19'
    SUB = '\x1A'
    ESC = '\x1B'
    FS  = '\x1C'
    GS  = '\x1D'
    RS  = '\x1E'
    US  = '\x1F'
# ---------------------- Escape sequences ----------------------
    ACS = '\x20'
    CZD = '\x21'
    C1D = '\x22'
    #      0x23 : registered control function indicator
    #      0x24 : multi-byte character set designator
    #GZDM4 = '\x24\x28'
    #G1DM4 = '\x24\x29'
    #G2DM4 = '\x24\x2A'
    #G3DM4 = '\x24\x2B'
    #G1DM6 = '\x24\x2D'
    #G2DM6 = '\x24\x2E'
    #G3DM6 = '\x24\x2F'
    DOCS = '\x25'
    IRR  = '\x26'
    #       0x27 : not defined
    GZD4 = '\x28'
    G1D4 = '\x29'
    G2D4 = '\x2A'
    G3D4 = '\x2B'
    #       0x2C : not defined
    G1D6 = '\x2D'
    G2D6 = '\x2E'
    G3D6 = '\x2F'
# ------------------ Private control functions -----------------
    #      0x30 - 0x3F : private use
# -------------------- C1 control functions --------------------
    PAD = '\x40' # from RFC 1345, not defined by ISO 6429
    HOP = '\x41' # from RFC 1345, not defined by ISO 6429
    BPH = '\x42'
    NBH = '\x43'
    IND = '\x44' # deprecated and withdrawn from ISO 6429
    NEL = '\x45'
    SSA = '\x46'
    ESA = '\x47'
    HTS = '\x48'
    HTJ = '\x49'
    VTS = '\x4A'
    PLD = '\x4B'
    PLU = '\x4C'
    RI  = '\x4D'
    SS2 = '\x4E'
    SS3 = '\x4F'
    DCS = '\x50'
    PU1 = '\x51'
    PU2 = '\x52'
    STS = '\x53'
    CCH = '\x54'
    MW  = '\x55'
    SPA = '\x56'
    EPA = '\x57'
    SOS = '\x58'
    SGCI= '\x59' # from RFC 1345, not defined by ISO 6429
    SCI = '\x5A'
    CSI = '\x5B'
    ST  = '\x5C'
    OSC = '\x5D'
    PM  = '\x5E'
    APC = '\x5F'
# --------------- Independent control functions ----------------
    DMI = '\x60'
    INT = '\x61'
    EMI = '\x62'
    RIS = '\x63'
    CMD = '\x64'
    #      0x65 - 0x6D : not defined
    LS2 = '\x6E'
    LS3 = '\x6F'
    #      0x70 - 0x7B : not defined
    LS3R = '\x7C'
    LS2R = '\x7D'
    LS1R = '\x7E'
# ----------------------- Miscellaneous ------------------------
    DEL = '\x7F'
    CC0_SET = {chr(val) for val in range(0x00, 0x20)}
    INT_SET = {chr(val) for val in range(0x20, 0x30)}
    PAR_SET = {chr(val) for val in range(0x30, 0x40)}
    FIN_SET = {chr(val) for val in range(0x40, 0x7F)}
    #ESC_SET = {chr(val) for val in range(0x20, 0x7F)}
    CC1_SET = {chr(val) for val in range(0x80, 0xA0)}
    DLM_SET = {DCS, SOS, OSC, PM, APC}
    FMT_SET = {BS, HT, LF, VT, FF, CR}
    CCH_SET = CC0_SET | {DEL} | CC1_SET
    ESC_SET = INT_SET | PAR_SET | FIN_SET
    STR_SET = ESC_SET | FMT_SET

#for val in range(0x40, 0x60):
#    name = CtrlFn.getname(chr(val))
#    if name:
#        setattr(CtrlFn, name + '_8', unichr(val + 0x40))

@valmap
class CtrlSeq:
    ICH = '\x40'
    CUU = '\x41'
    CUD = '\x42'
    CUF = '\x43'
    CUB = '\x44'
    CNL = '\x45'
    CPL = '\x46'
    CHA = '\x47'
    CUP = '\x48'
    CHT = '\x49'
    ED  = '\x4A'
    EL  = '\x4B'
    IL  = '\x4C'
    DL  = '\x4D'
    EF  = '\x4E'
    EA  = '\x4F'
    DCH = '\x50'
    SSE = '\x51'
    CPR = '\x52'
    SU  = '\x53'
    SD  = '\x54'
    NP  = '\x55'
    PP  = '\x56'
    CTC = '\x57'
    ECH = '\x58'
    CVT = '\x59'
    CBT = '\x5A'
    SRS = '\x5B'
    PTX = '\x5C'
    SDS = '\x5D'
    SIMD= '\x5E'
    #      0x5F : not defined
    HPA = '\x60'
    HPR = '\x61'
    REP = '\x62'
    DA  = '\x63'
    VPA = '\x64'
    VPR = '\x65'
    HVP = '\x66'
    TBC = '\x67'
    SM  = '\x68'
    MC  = '\x69'
    HPB = '\x6A'
    VPB = '\x6B'
    RM  = '\x6C'
    SGR = '\x6D'
    DSR = '\x6E'
    DAQ = '\x6F'
    #      0x70 - 0x7E : private use

class CtrlObj(object):
    __slots__ = ('cmd', 'argstr', 'optstr', 'arg')
    def __init__(self, cmd):
        self.cmd = cmd
        self.argstr = ''
        self.optstr = ''
        self.arg = None
    def __str__(self):
        ret = ''
        if self.cmd not in CtrlFn.CC0_SET:
            ret += CtrlFn.ESC
        ret += self.cmd + self.argstr
        if self.cmd in CtrlFn.DLM_SET and self.optstr not in CtrlFn.CC0_SET:
            ret += CtrlFn.ESC
        ret += self.optstr
        return ret
    def __repr__(self):
        ret = '<{}>{}'.format(CtrlFn.getname(self.cmd), self.argstr)
        if self.cmd == CtrlFn.CSI:
            ret += '<{}>'.format(CtrlSeq.getname(self.optstr))
        elif self.cmd in CtrlFn.DLM_SET:
            ret += '<{}>'.format(CtrlFn.getname(self.optstr))
        else:
            ret += self.optstr
        if self.__class__ is CtrlErrorObj:
            ret += '{ERROR}'
        return ret
    def caretnotation(self):
        if self.cmd in CtrlFn.CC0_SET:
            ret = '^' + chr(ord(self.cmd) + 0x40)
        else:
            ret = '^[' + self.cmd
        ret += self.argstr
        if self.cmd in CtrlFn.DLM_SET and self.optstr not in CtrlFn.CC0_SET:
            ret += '^['
        ret += self.optstr
        return ret
    def getarg(self, num, dflt):
        if self.arg is None:
            self.arg = tuple([int(val) if val.isdigit() else (val or None)
                              for val in self.argstr.split(';')])
        val = self.arg[num] if num < len(self.arg) else None
        return  val if val is not None else dflt
    def seterror(self):
        self.__class__ = CtrlErrorObj

class CtrlErrorObj(CtrlObj):
    __slots__ = ()

class CtrlParser(object):
    
    def __init__(self, encoding):
        codec = codecs.getincrementaldecoder(encoding)
        self._decode = codec('replace').decode
        codec = codecs.getincrementalencoder(encoding)
        self._encode = codec('replace').encode
        self._state = None
        self._ctrl = None

    def combine(self, items):
        return self._encode(''.join(unicode(item) for item in items))

    def parse(self, data):
        ESC = CtrlFn.ESC
        CSI = CtrlFn.CSI
        SCI = CtrlFn.SCI
        BEL = CtrlFn.BEL
        ST  = CtrlFn.ST
##        ST_8b = unichr(ord(ST) + 0x40)
        CCH_SET = CtrlFn.CCH_SET
        INT_SET = CtrlFn.INT_SET
        PAR_SET = CtrlFn.PAR_SET
        FIN_SET = CtrlFn.FIN_SET
        DLM_SET = CtrlFn.DLM_SET
        ESC_SET = CtrlFn.ESC_SET
        STR_SET = CtrlFn.STR_SET
        
        text = self._decode(data)
        state = self._state
        ctrl = self._ctrl
        res = []
        idx = 0
        
        for jdx, ch in enumerate(text):
            
            if state is None:
                if ch not in CCH_SET:
                    continue
                if jdx > idx:
                    res.append(text[idx:jdx])
                if ch == ESC:
                    state = ESC
                    continue
                elif ch in CC1_SET:
                    state = ESC
                    ch = unichr(ord(ch) - 0x40)
                else:
                    ctrl = CtrlObj(ch)
                    res.append(ctrl)
                    ctrl = None
                    idx = jdx + 1
                    continue
        
            if state is ESC:
                ctrl = CtrlObj(ch)
                if ch == CSI:
                    state = CSI
                elif ch == SCI:
                    state = SCI
                elif ch in DLM_SET:
                    state = DLM_SET
                elif ch in INT_SET:
                    state = INT_SET
                else:
                    if ch not in ESC_SET:
                        ctrl.seterror()
                        ctrl.cmd = ESC
                        ctrl.optstr = ch
                    res.append(ctrl)
                    state = None
                    ctrl = None
                    idx = jdx + 1
        
            elif state is CSI:
                if ch in PAR_SET and not ctrl.optstr:
                    ctrl.argstr += ch
                elif ch in INT_SET:
                    ctrl.optstr += ch
                else:
                    ctrl.optstr += ch
                    if ch not in FIN_SET:
                        ctrl.seterror()
                    res.append(ctrl)
                    state = None
                    ctrl = None
                    idx = jdx + 1
        
            elif state is SCI:
                ctrl.optstr = ch
                if ch not in STR_SET:
                    ctrl.seterror()
                res.append(ctrl)
                state = None
                ctrl = None
                idx = jdx + 1
        
            elif state is DLM_SET:
                if ch in STR_SET and not ctrl.optstr:
                    ctrl.argstr += ch
                elif ch == ESC and not ctrl.optstr:
                    ctrl.optstr = True
                else:
                    if ch != ST if ctrl.optstr else ch != BEL:
                        ctrl.seterror()
                    ctrl.optstr = ch
                    res.append(ctrl)
                    state = None
                    ctrl = None
                    idx = jdx + 1
        
            elif state is INT_SET:
                if ch in INT_SET:
                    ctrl.optstr += ch
                else:
                    ctrl.optstr += ch
                    if ch not in FIN_SET:
                        ctrl.seterror()
                    res.append(ctrl)
                    state = None
                    ctrl = None
                    idx = jdx + 1
            
        if state is None and idx <= jdx:
            res.append(text[idx:])
        self._state = state
        self._ctrl = ctrl
        return res
        
def cook(data):
    res = []
    format = (0, 0, 0)
    for item in data:
        if type(item) is unicode:
            line.append(item, format)
        elif item.cmd == CSI and item.opt == SGR:
            arg = cmd.getarg(0, 0)
            if arg == 0:
                fgcol, bgcol, format = 0, 0, 0
            if arg == 1:
                
                style = (fgcol, bgcol, 
    pass


##class PageBuffer(object):
##    def __init__(self, size):
##        self._buffer = [BufferElement()]
##        self._head = 0
####        self._tail = 0
####    def isempty(self):
####        return self._head == self._tail
##    def add(self, text):
##        elem = self._buffer[self._head]
##        elem.text += text
##    def tail(self):
##        return (self_head + 1) % self._length if len(self._buffer) == self._length else 0
##    def wintop(self):
##        self._winbase - self._winlen
##    def incr(self):
##        if len(self._buffer) < self._length:
##            self._head += 1
##            self._buffer.append(BufferElement())
##        else:
##            self._head = (self._head + 1) % self._length
##            self._buffer[self._head] = BufferElement()
####        if self._head == self._tail:
####            self._tail = (self._tail + 1) % self._length
##        if self._paused:
##            pass
##        else:
##            self._winbase = self._head
##            self._wintop = 
        
        
##class TerminalEmulator(wx.Window):
##    def __init__(self, parent, id, title):
##        wx.Window.__init__(self, parent, id, title)
##    def AddText(self, text):
##        pass
##    @property
##    def PageSize(self):
##        return self._pagesize
##    @PageSize.setter(self, size):
##        pass
    
#class AnsiTerminal(wx.Window):
#    def __init__(self, parent, id, title):
#        wx.Window.__init__(self, parent, id, title)
##        wx.EVT_PAINT(self, self.OnPaint)
#        self.Bind(wx.EVT_PAINT, self.OnPaint)
#        self.Bind(wx.EVT_SIZE, self.OnSize)
#    def Draw(self, dc):
#        pass
#    def OnSize(self, evt):
#        size = self.ClientSize
#        self.winbuf = wx.EmptyBitmap(size)
#        self.UpdateDrawing()
#    def OnPaint(self, evt):
##        dc = wx.AutoBufferedPaintDCFactory(self)
#        dc = wx.BufferedPaintDC(self, self.winbuf)
